//
// Created by yasin on 2024/9/6.
//

#include "rtc_client.h"
#include "api/video/video_source_interface.h"
#include "rtc_client/peer_connection_dependency_factory.h"
#include "signaling_client.h"
#include "rtc_log.h"
#include "ohos_video_source_wrapper.h"

constexpr int CAMERA_FPS = 30;
constexpr int CAMERA_WIDTH = 720;
constexpr int CAMERA_HEIGHT = 1280;
constexpr int SCREEN_CAST_WIDTH = 480;// 480;
constexpr int SCREEN_CAST_HEIGHT = 640;// 640;

namespace ohosrtc {

RTCClient::RTCClient() {
  pcd_factory_ = PeerConnectionDependencyFactory::Create();
  media_thread_ = rtc::Thread::Create();
  media_thread_->SetName("media_thread", nullptr);
  media_thread_->Start();
  audio_manager_ = std::make_shared<OHOSAudioManager>(reinterpret_cast<webrtc::PeerConnectionFactoryInterface *>(pcd_factory_->GetOHOSPeerConnectionFactory()->peer_connection_factory().get()), pcd_factory_->GetOHOSPeerConnectionFactory()->worker_thread());
}

RTCClient *RTCClient::Inst() {
  static RTCClient *inst = nullptr;
  if (!inst) {
    inst = new RTCClient;
  }
  return inst;
}

void RTCClient::Connect(const ohosrtc::RoomConnectionParameters &params) {
  RTC_LOG << "room_id=" << params.room_id << " room_url=" << params.room_url;
  
  signaling_client_ = std::make_shared<SignalingClient>();
  signaling_client_->Connect(params.room_url, params.room_id, [this](const RoomAccessInfo &info) {
    this->OnSignalConnected();
  }, [this](const std::string &error) {
    this->OnSignalConnectFailed(error);
  });
}

void RTCClient::SetRemoteRender(std::shared_ptr<webrtc::VideoRenderInterface> render) {
	RTC_LOG << "set remote render" << render;
  remote_render_ = render;
}

void RTCClient::SetLocalRender(std::shared_ptr<webrtc::VideoRenderInterface> render) {
  RTC_LOG << "set local render" << render;
	local_render_ = render;
}

void RTCClient::OnAddStream(OHOSMediaStreamPtr stream) {
  RTC_LOG << "OnAddStream attach render, stream_id=" << stream->stream_id();
  if(remote_streams_.size()>0){
    auto res = remote_streams_.top();
    auto track = res->GetVideoTracks().front();
    track->set_enabled(false);
    track->video_track()->RemoveSink(remote_render_.get());
  }
  remote_streams_.push(stream);
  if(!stream) {
    RTC_LOG << "error: OnAddStream stream cannot be null";
    return;
  }
  if(!remote_render_) {
    RTC_LOG << "error: OnAddStream remote_render_ cannot be null";
    return;
  }
  if(stream->GetVideoTracks().empty()) {
    RTC_LOG << "error: OnAddStream GetVideoTracks size can not be zero";
    return;
  }
  RTC_LOG << "attach render, stream_id=" << stream->stream_id() << " tracks size: "<<stream->GetVideoTracks().size();
  auto track = stream->GetVideoTracks().front();
  track->set_enabled(true);
  track->video_track()->AddOrUpdateSink(remote_render_.get(), rtc::VideoSinkWants());
}

void RTCClient::OnRemoveStream(OHOSMediaStreamPtr stream) {
  if (stream && remote_render_ && !stream->GetVideoTracks().empty()) {
    RTC_LOG << "detach render, stream_id=" << stream->stream_id();
    auto track = stream->GetVideoTracks().front();
    track->video_track()->RemoveSink(remote_render_.get());
  }
  remote_streams_.pop();
  if(remote_streams_.size()>0){
    auto res = remote_streams_.top();
    if (res && remote_render_ && !res->GetVideoTracks().empty()) {
      RTC_LOG << "attach render, stream_id=" << res->stream_id();
      auto track = res->GetVideoTracks().front();
      track->set_enabled(true);
      track->video_track()->AddOrUpdateSink(remote_render_.get(), rtc::VideoSinkWants());
    }
  }
}

void RTCClient::OnSignalConnected() {
  RTC_LOG << "signal connected";
  CreateStreamChannel();
  CreateCameraSender(true);
  signaling_client_->SetRemoteSdpHandler([this](webrtc::SessionDescriptionInterface* sdp) {
    std::string out;
    sdp->ToString(&out);
    RTC_LOG << "on remote sdp \n" << out;
    if (sdp->GetType() == webrtc::SdpType::kOffer) {
      RTC_LOG << "handle remote sdp offer";
      HandleP2pOffer(std::move(sdp));
    } else if (sdp->GetType() == webrtc::SdpType::kAnswer) {
      RTC_LOG << "handle remote sdp answer";
      if (p2p_channel_) {
        p2p_channel_->SetRemoteDescription(sdp);
      }
    }
  });
  
  signaling_client_->SetRemoteIceCandidateHandler([this](webrtc::IceCandidateInterface* ice_candidate) {
    RTC_LOG << "handle remote ice candidate";
    // peer_connection add remote candidate
    if (p2p_channel_) {
      p2p_channel_->SetRemoteIceCandidate(ice_candidate);
    }
  });
  
  signaling_client_->SetRemoteByeHandler([this]() {
    RTC_LOG << "handle bye";
    // peer_connection add remote candidate
    Disconnect();
    if (this->callback_) {
      this->callback_();
    }
  });
  
  // the peer might already send offer, need to handle the offer in remote sdp handler
  signaling_client_->ConsumeRoomMessages();
  auto access_info = signaling_client_->GetRoomAccessInfo();
  if (access_info.is_initiator) {
    CreateP2pOffer();
  }
}

void RTCClient::OnSignalConnectFailed(const std::string &error) {
  RTC_LOG << "signal not connected";
}

void RTCClient::Hangup() {
  RTC_LOG << "Hangup";
  if (!signaling_client_) {
    RTC_LOG << "signal client not inited";
    return;
  }
  Disconnect();
}

void RTCClient::SetHangupCallback(std::function<void()> callback) {
  if (callback) {
    callback_ = std::move(callback);
  }
}

void RTCClient::Disconnect() {
  RTC_LOG << "disconnect";
  if (!signaling_client_) {
    RTC_LOG << "signal client not inited";
    return;
  }
  
  if(camera_capture_) {
    camera_capture_->StopCapture();
  }
  
  if(screen_capture_) {
    screen_capture_->StopCapture();
  }  
  
  signaling_client_->Disconnect();
  if (p2p_channel_) {
    p2p_channel_->Close();
  }
  p2p_channel_.reset();
}

void RTCClient::CreateP2pOffer() {
  RTC_LOG << "RTCClient create p2p offer";
  p2p_channel_->CreateP2pOffer(capture_local_stream_);
}

void RTCClient::StartScreenCapture(){
  if(!screen_capture_) {
    screen_capture_ = std::make_shared<NJSScreenCapture>(SCREEN_CAST_WIDTH, SCREEN_CAST_HEIGHT, CAMERA_FPS, reinterpret_cast<webrtc::PeerConnectionFactoryInterface *>(pcd_factory_->GetOHOSPeerConnectionFactory()->peer_connection_factory().get()), pcd_factory_->GetOHOSPeerConnectionFactory()->worker_thread());
  }
  auto video_source_wrapper = std::make_shared<OHOSVideoSourceWrapper>(pcd_factory_->GetScreenSource());
  screen_capture_->BindSink(video_source_wrapper);
  RTC_LOG << "StartCapture start";
  screen_capture_->StartCapture(); 
//   if(remote_render_){
//     RTC_LOG << "bind remote render";
//     screen_capture_->BindSink(remote_render_);
//   }  
  RTC_LOG << "StartCapture end";    
}

void RTCClient::CreateCameraSender(bool is_front) {
  media_thread_->BlockingCall([&] {
      CreateCameraSender_m(is_front);
    });
}

void RTCClient::CreateCameraSender_m(bool is_front) {
  RTC_LOG << "RTCClient CreateCameraSender" << rtc::Thread::Current();
//  rtc::Thread::Current();
  RTC_LOG << "RTCClient CreateCameraSender";
  if (!camera_capture_) {
    RTC_LOG << "RTCClient GetCameraDeviceId start";
    std::string camera_id = NJSCameraCapture::GetCameraDeviceId(is_front);
    RTC_LOG << "RTCClient GetCameraDeviceId end" << camera_id;
    if (camera_id.length() > 0) {
      camera_capture_ = std::make_shared<NJSCameraCapture>(CAMERA_WIDTH,CAMERA_HEIGHT,CAMERA_FPS,camera_id);
      auto video_source_wrapper = std::make_shared<OHOSVideoSourceWrapper>(pcd_factory_->GetVideoSource());
      camera_capture_->BindSink(video_source_wrapper);
      RTC_LOG << "StartCapture start";
      camera_capture_->StartCapture(); 
      RTC_LOG << "StartCapture end";
      if (local_render_) {
        RTC_LOG << "bind local render";
        camera_capture_->BindSink(local_render_);
      }
    }
  }
}

void RTCClient::FlipCamera(bool is_front) {
  media_thread_->BlockingCall([&] {
    FlipCamera_m(is_front);
  });
}

void RTCClient::FlipCamera_m(bool is_front) {
  RTC_LOG << "RTCClient FlipCamera";
  RTC_LOG << "RTCClient FlipCamera" << rtc::Thread::Current();
  if (camera_capture_) {
    camera_capture_->StopCapture();
//    camera_capture_.reset();
    //rebuild
    RTC_LOG << "RTCClient GetCameraDeviceId start";
    std::string camera_id = NJSCameraCapture::GetCameraDeviceId(is_front);
    RTC_LOG << "RTCClient GetCameraDeviceId end" << camera_id;
    if (camera_id.length() > 0) {
      camera_capture_ = std::make_shared<NJSCameraCapture>(CAMERA_WIDTH,CAMERA_HEIGHT,CAMERA_FPS,camera_id);
      auto video_source_wrapper = std::make_shared<OHOSVideoSourceWrapper>(pcd_factory_->GetVideoSource());
      camera_capture_->BindSink(video_source_wrapper);
      RTC_LOG << "StartCapture start";
      camera_capture_->StartCapture(); 
      RTC_LOG << "StartCapture end";
      if (local_render_) {
        RTC_LOG << "bind local render";
        camera_capture_->BindSink(local_render_);
      }
    }
  }
}

void RTCClient::MuteMic() {
  RTC_LOG << "RTCClient MuteMic";
  audio_manager_->Mute();
}

void RTCClient::UnMuteMic() {
  RTC_LOG << "RTCClient UnMuteMic";
  audio_manager_->UnMute();
}

void RTCClient::CreateStreamChannel() {
  capture_local_stream_ = pcd_factory_->CreateOHOSLocalStream(true, true, false);
  auto config = GetPeerConnectionChannelConfiguration();
  p2p_channel_ = PeerConnectionChannel::Create(config, pcd_factory_, signaling_client_);
	auto on_stream_add = std::bind(&RTCClient::OnAddStream, this, std::placeholders::_1);
	auto on_stream_remove = std::bind(&RTCClient::OnRemoveStream, this, std::placeholders::_1);
	p2p_channel_->SetStreamEventHandler(on_stream_add, on_stream_remove);
  p2p_channel_->AddStream(capture_local_stream_->MediaStream());
}

void RTCClient::StartScreenCast(){
  RTC_LOG << "RTCClient StartScreenCast";
  if(p2p_channel_) {
    auto config = GetPeerConnectionChannelConfiguration();
    if(!screen_local_stream_) {
      screen_local_stream_ = pcd_factory_->CreateOHOSLocalStream(true, true, true);
    }
    StartScreenCapture();
    p2p_channel_->setLocalStream(screen_local_stream_);
    p2p_channel_->AddStream(screen_local_stream_->MediaStream());
    p2p_channel_->CreateOffer();
  }
}

void RTCClient::StopScreenCast() {
  RTC_LOG << "RTCClient StopScreenCast";
  if (p2p_channel_) {
    if(screen_capture_) {
      screen_capture_->StopCapture();
      screen_capture_ = nullptr;
    }
    p2p_channel_->setLocalStream(screen_local_stream_);
    p2p_channel_->RemoveStream(screen_local_stream_->MediaStream());
    screen_local_stream_ = nullptr;
    p2p_channel_->CreateOffer();
  }
}

// set remote sdp -> create answer -> set local sdp
void RTCClient::HandleP2pOffer(webrtc::SessionDescriptionInterface *sdp) {
  RTC_LOG << "handle p2p offer";
  if (p2p_channel_) {
    RTC_LOG << "already has channel";
    p2p_channel_->SetRemoteDescription(sdp);
  }
}

PeerConnectionChannelConfiguration RTCClient::GetPeerConnectionChannelConfiguration() {
  PeerConnectionChannelConfiguration config;
  for (auto &ice_server : signaling_client_->GetRoomAccessInfo().ice_servers) {
    webrtc::PeerConnectionInterface::IceServer webrtc_ice_server;
    webrtc_ice_server.uri = ice_server.url;
    webrtc_ice_server.urls.emplace_back(ice_server.url);
    webrtc_ice_server.username = ice_server.username;
    webrtc_ice_server.password = ice_server.passwd;
    config.servers.emplace_back(std::move(webrtc_ice_server));
  }
  config.tcp_candidate_policy = webrtc::PeerConnectionInterface::kTcpCandidatePolicyDisabled;
  config.bundle_policy = webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle;
  config.rtcp_mux_policy = webrtc::PeerConnectionInterface::kRtcpMuxPolicyRequire;
  config.continual_gathering_policy = webrtc::PeerConnectionInterface::GATHER_CONTINUALLY;
  config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
  return config;
}

}